package com.sunlands.zlcx.usercenter.service;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.sunlands.zlcx.usercenter.client.UserFriendsService;
import com.sunlands.zlcx.usercenter.constant.LearningStatus;
import com.sunlands.zlcx.usercenter.constant.NameStatus;
import com.sunlands.zlcx.usercenter.constant.RedisKeyConst;
import com.sunlands.zlcx.usercenter.constant.YN;
import com.sunlands.zlcx.usercenter.domain.*;
import com.sunlands.zlcx.usercenter.domain.count.SumUser7DaysLearnDO;
import com.sunlands.zlcx.usercenter.exception.CheckRunException;
import com.sunlands.zlcx.usercenter.exception.SendExceptionUtil;
import com.sunlands.zlcx.usercenter.repository.*;
import com.sunlands.zlcx.usercenter.service.status.common.AbstractStudyStatus;
import com.sunlands.zlcx.usercenter.service.status.common.StatusContext;
import com.sunlands.zlcx.usercenter.service.status.common.StatusContextFactory;
import com.sunlands.zlcx.usercenter.util.*;
import com.sunlands.zlcx.usercenter.vo.*;
import com.sunlands.zlcx.usercenter.vo.feign.UserFriendInfo;
import com.sunlands.zlcx.usercenter.vo.response.BusinessResult;
import com.sunlands.zlcx.usercenter.vo.response.PageResultVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.http.util.Asserts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.sunlands.zlcx.usercenter.config.RedisConfig.CacheKeys.USERS_LEARN_LEAVE_MSG_SET;
import static com.sunlands.zlcx.usercenter.domain.UsersDO.StatusEnums.LEARN_TIME_ROOM_LEARNING;
import static com.sunlands.zlcx.usercenter.domain.UsersLearnTimingDO.TypeEnums.ORDERNARY_COUNT_TIME;

/**
 * @author gaoyanfeng
 * description: 学习目标
 */
@Service
@Slf4j
public class UsersStudyServiceImpl implements BeanPropertiesUtil {

    private static int PAGE_SIZE = 100;
    @Autowired
    SendExceptionUtil sendExceptionUtil;

    @Transactional(rollbackFor = Exception.class)
    public Boolean addUsersStudyInfo(UsersStudyInfoVO usersStudyInfoVO) {
        UsersVO usersDO = usersService.findOne(usersStudyInfoVO.getUserId());
        Asserts.notNull(usersDO, "没有查询到对应的用户信息 userId = " + usersStudyInfoVO.getUserId());
        UsersTargetRelationDO usersTargetRelationDO = new UsersTargetRelationDO();
        UsersTargetRelationVO usersTargetRelationVO = targetService.getUserTarget(usersStudyInfoVO.getUserId());
        copyNonNullProperties(usersTargetRelationVO, usersTargetRelationDO);

        StatusContext statusContext = statusContextFactory.fetchStatusContext(UsersStudyInfoDO.TypeEnums.create(usersStudyInfoVO.getType()));
        statusContext.doLearnChange(usersStudyInfoVO, usersTargetRelationDO);
        return true;
    }

    public UsersStudyInfoDO update(UsersStudyInfoDO usersStudyInfoDO) {
        return usersStudyInfoRepository.save(usersStudyInfoDO);
    }

    public UsersLearningResultVO getLearningResult(UsersStudyInfoVO usersStudyInfoVO) {
        UsersLearningResultVO usersLearningResultVO = new UsersLearningResultVO();
        int studyMinutes = CalendarUtil.seconds2Minutes(usersStudyInfoVO.getStudyMinuteSeconds()).intValue();

        usersLearningResultVO.setStudyMinutes(studyMinutes == 0 ? 1 : studyMinutes);
        Long time = usersLearnTimingRepository.sumLearningTimeToday(usersStudyInfoVO.getUserId());
        if (null != time && time > 0) {
            usersLearningResultVO.setTodayHours(DateUtil.minutesToHours(time.longValue()));
        } else {
            usersLearningResultVO.setTodayHours(new BigDecimal(0));
        }
        usersLearningResultVO.setStudyDays(usersLearnTimingRepository.countLearningDaysWeek(usersStudyInfoVO.getUserId()));
        usersLearningResultVO.setUserId(usersStudyInfoVO.getUserId());
        usersLearningResultVO.setStudyInfoName(usersStudyInfoVO.getStudyInfoName());
        return usersLearningResultVO;
    }

    public UsersLearningResultVO getLearningResult(Long userId) {
        UsersLearningResultVO usersLearningResultVO = new UsersLearningResultVO();
        UsersLearnTimingDO learnTimingDO = usersLearnTimingRepository.findFirstByUserIdOrderByCreateTimeDesc(userId);
        int studyMinutes = learnTimingDO.getStudyMinutes().intValue();
        usersLearningResultVO.setStudyMinutes(studyMinutes == 0 ? 1 : studyMinutes);
        Long time = usersLearnTimingRepository.sumLearningTimeToday(userId);
        if (null != time && time > 0) {
            usersLearningResultVO.setTodayHours(DateUtil.minutesToHours(time.longValue()));
        } else {
            usersLearningResultVO.setTodayHours(new BigDecimal(0));
        }
        usersLearningResultVO.setStudyDays(usersLearnTimingRepository.countLearningDaysWeek(userId));
        usersLearningResultVO.setUserId(userId);
        usersLearningResultVO.setStudyInfoName(learnTimingDO.getLearnName());
        return usersLearningResultVO;
    }

    @Async("gyfTaskExecutor")
    public Future<PageResultVO<UsersStudyInfoVO>> getStudyList(Integer userId, Integer page, Integer pageSize) {
        Specification<UsersStudyInfoDO> specification = (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicateList = Lists.newArrayList();
            predicateList.add(criteriaBuilder.equal(root.get("userId").as(Long.class), userId));
            predicateList.add(criteriaBuilder.not(root.get("type").in(Lists.newArrayList(26, 27))));
            return criteriaBuilder.and(predicateList.toArray(new Predicate[predicateList.size()]));
        };
        Sort sort = new Sort(Sort.Direction.DESC, Lists.newArrayList("createTime"));
        PageRequest pageRequest = new PageRequest(page, pageSize, sort).previous();
        Page<UsersStudyInfoDO> usersStudyInfoDOPage = usersStudyInfoRepository.findAll(specification, pageRequest);
        List<UsersStudyInfoDO> content = usersStudyInfoDOPage.getContent();
        int size = content.size();
        int count = 0;
        PageResultVO<UsersStudyInfoVO> result = new PageResultVO<UsersStudyInfoVO>();
        Future<PageResultVO<UsersStudyInfoVO>> resultVOFuture = new AsyncResult<>(result);
        if (content != null && size > 0) {
            List<UsersStudyInfoVO> usersStudyInfoVOS = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                UsersStudyInfoDO usersStudyInfoDO = content.get(i);
                UsersStudyInfoVO usersStudyInfoVO = UsersStudyInfoVO.from(usersStudyInfoDO);
                Date countTime = usersStudyInfoDO.getCountTime();
                String dateString = CalendarUtil.getDateString(countTime, "yyyy-MM-dd");
                String today = CalendarUtil.getDateString(new Date(), "yyyy-MM-dd");
                if (i == 0 && CalendarUtil.getDayZeroTime(CalendarUtil.getDateHoursBefore(new Date(), 3)) > CalendarUtil.getDayZeroTime(countTime)) {
                    String tpageLastKey = RedisKeyConst.getTpageLast(page - 1, pageSize, userId);
                    Object o = redisTemplate.opsForValue().get(tpageLastKey);
                    if (null != o) {
                        String pageLastValue = (String) o;
                        if (!pageLastValue.equals(dateString)) {
                            count = getCount(count, usersStudyInfoVOS, today, countTime, dateString);
                        }
                    } else {
                        count = getCount(count, usersStudyInfoVOS, today, countTime, dateString);
                    }

                }
                usersStudyInfoVOS.add(usersStudyInfoVO);
                if (i + 1 < size) {
                    UsersStudyInfoDO usersStudyInfoDO1 = content.get(i + 1);
                    Date countTime1 = usersStudyInfoDO1.getCountTime();
                    String dateString1 = CalendarUtil.getDateString(countTime1, "yyyy-MM-dd");
                    count = getCount(count, usersStudyInfoVOS, dateString, countTime1, dateString1);
                } else if (i + 1 == size) {
                    String tpageLastKey = RedisKeyConst.getTpageLast(page, pageSize, userId);
                    redisTemplate.opsForValue().set(tpageLastKey, dateString, 8, TimeUnit.HOURS);
                }
            }
            result.setRows(usersStudyInfoVOS);
            result.setTotal(size + count); // 当前页total
        }
        return resultVOFuture;
    }


    @Async("gyfTaskExecutor")
    public Future<List<UsersLearnTimingDO>> getLearnList(Long userId, String date) {
        Date start = DateUtil.strToDate(DateUtil.getNextDay(date, "0"));
        Date end = DateUtil.strToDate(DateUtil.getNextDay(date, "1"));
        List<UsersLearnTimingDO> usersStudyInfoDOList = usersLearnTimingRepository.findByUserIdAndBetweenDate(userId, start, end);
        return new AsyncResult<>(usersStudyInfoDOList);
    }

    private int getCount(int count, List<UsersStudyInfoVO> usersStudyInfoVOS, String dateString, Date createTime1, String dateString1) {
        if (!dateString.equals(dateString1)) {
            UsersStudyInfoVO weekVO = new UsersStudyInfoVO();
            weekVO.setCreateTime(CalendarUtil.parseDate(dateString1, "yyyy-MM-dd"));
            weekVO.setCountTime(CalendarUtil.parseDate(dateString1, "yyyy-MM-dd"));
            String week = CalendarUtil.getWeek(createTime1);
            weekVO.setWeek(week);
            if (System.currentTimeMillis() - weekVO.getCountTime().getTime() < 172800000) {
                weekVO.setStudyInfoName("Yesterday");
            }
            weekVO.setType(UsersStudyInfoDO.TypeEnums.WEEK.getCode());
            weekVO.setTypeName(week);
            usersStudyInfoVOS.add(weekVO);
            count++;
        }
        return count;
    }

    public PageResultVO<UsersLearnTimingVO> getLearnTimingList(Integer userId, Integer tailId, Integer page, Integer pageSize) {
        Specification<UsersLearnTimingDO> specification = new Specification<UsersLearnTimingDO>() {
            @Override
            public Predicate toPredicate(Root<UsersLearnTimingDO> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicateList = Lists.newArrayList();
                predicateList.add(criteriaBuilder.equal(root.get("userId").as(Long.class), userId));
                predicateList.add(criteriaBuilder.equal(root.get("status").as(Integer.class), YN.YES.getCode()));
                predicateList.add(criteriaBuilder.greaterThan(root.get("id").as(Long.class), tailId.longValue()));
                return criteriaBuilder.and(predicateList.toArray(new Predicate[predicateList.size()]));
            }
        };
        Sort sort = new Sort(Sort.Direction.DESC, Lists.newArrayList("createTime"));
        PageRequest pageRequest = new PageRequest(page, pageSize, sort).previous();
        Page<UsersLearnTimingDO> usersStudyInfoDOPage = usersLearnTimingRepository.findAll(specification, pageRequest);
        List<UsersLearnTimingDO> content = usersStudyInfoDOPage.getContent();
        int size = content.size();
        PageResultVO<UsersLearnTimingVO> result = new PageResultVO<>();
        if (content != null && size > 0) {
            List<UsersLearnTimingVO> usersStudyInfoVOS = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                UsersLearnTimingDO usersLearnTimingDO = content.get(i);
                UsersLearnTimingVO usersLearnTimingVO = UsersLearnTimingVO.from(usersLearnTimingDO);
                usersStudyInfoVOS.add(usersLearnTimingVO);
            }
            result.setRows(usersStudyInfoVOS);
            result.setTotal(size);
        }
        return result;
    }

    public Integer getUsersStudyTime(String createTime, List<Integer> userIdList) {
        return usersStudyInfoRepository.sumStudyMinute(userIdList, createTime);
    }

    /**
     * 复制歘一个不查询缓存的方法。临时解决用户学习状态错误的问题
     *
     * @param userId userId
     * @return
     */
    public JSONObject getLearnTime(Long userId, String version) {
        UsersVO usersVO = usersService.findOne(userId);

        if (Objects.isNull(usersVO)) {
            throw new CheckRunException("用户不存在" + userId);
        }

        //旧版兼容处理，如果正在学习中，连麦抛出异常
        if (LEARN_TIME_ROOM_LEARNING.code.equals(usersVO.getStatus()) && Version.compareVersion(version, "3.4.0") < 0) {
            AbstractStudyStatus.checkStatus(UsersStudyInfoDO.TypeEnums.LIAN_MAI_START.code, usersVO);
        }
        Date startLearnTime = usersVO.getStartLearnTime();
        if (LearningStatus.ifOrdernaryLearn(usersVO.getStatus()) && null == startLearnTime) {
            throw new CheckRunException("用户学习状态不正确" + userId);
        }
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("learnStatus", usersVO.getStatus());
        if (null == startLearnTime) {
            jsonObject.put("learnTime", 0);
            return jsonObject;
        }
        String pauseKey = RedisKeyConst.getPauseLearnTimeByUserid(userId);
        String continueKey = RedisKeyConst.getContinueLearnTimeByUserid(userId);
        List<Date> pauseList = redisTemplate.opsForList().range(pauseKey, 0, -1);
        log.debug("userid={}， pauseList={}", userId, pauseList);
        List<Date> continueList = redisTemplate.opsForList().range(continueKey, 0, -1);
        log.debug("userid={}， continueList={}", userId, continueList);
        long pause = 0L;
        long continu = 0L;
        if (AssertUtil.isNotEmpty(pauseList)) {
            for (Date p : pauseList) {
                pause += p.getTime();
            }
        }
        if (AssertUtil.isNotEmpty(continueList)) {
            for (Date c : continueList) {
                continu += c.getTime();
            }
        }
        long studySeconds = 0L;
        if (LearningStatus.LEARN_TIMING_PAUSE == LearningStatus.getLearningStatus(usersVO.getStatus())) {
            studySeconds = pause - continu - startLearnTime.getTime();
            log.debug("LEARN_TIMING_PAUSE userid={}， studySeconds={}", userId, studySeconds);
        } else if (LearningStatus.LEARN_TIMING == LearningStatus.getLearningStatus(usersVO.getStatus())) {
            studySeconds = System.currentTimeMillis() + pause - continu - startLearnTime.getTime();
            log.debug("LEARN_TIMING userid={}， studySeconds={}", userId, studySeconds);
        } else if (LearningStatus.LEARN_TIMING_CONINUE == LearningStatus.getLearningStatus(usersVO.getStatus())) {
            studySeconds = System.currentTimeMillis() + pause - continu - startLearnTime.getTime();
            log.debug("LEARN_TIMING_CONINUE userid={}， studySeconds={}", userId, studySeconds);
        } else {
            log.info("else userid={}， studySeconds={}", userId, studySeconds);
            log.info("用户当前学习状态 status={}", LearningStatus.getLearningStatus(usersVO.getStatus()).getTips());
            jsonObject.put("learnTime", 0);
        }
        jsonObject.put("learnTime", studySeconds > 28800000L ? 28800L : studySeconds / 1000);
        return jsonObject;
    }

    public UsersLearnTimingDO findLatestLearnTiming(Long userId) {
        return usersLearnTimingRepository.findFirstByUserIdOrderByCreateTimeDesc(userId);
    }

    public UsersLearnTimingDO findLatestOnlyLearnTiming(Long userId) {
        return usersLearnTimingRepository.findFirstByUserIdAndTypeNotOrderByCreateTimeDesc(userId, UsersLearnTimingDO.TypeEnums.LIAN_MAI.code);
    }

    public List<ContentCountVO> countByContentIds(List<Long> contentIds) {
        List<Object[]> objects = usersLearnTimingRepository.findByContentIds(contentIds);
        List<ContentCountVO> contentCountVOS = null;
        try {
            contentCountVOS = EntityUtils.objectToBean(objects, ContentCountVO.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return contentCountVOS;
    }


    public JSONObject getLearnTimeByUserId(Long userId) {
        UsersVO usersVO = usersService.findOne(userId);
        if (null == usersVO) {
            throw new CheckRunException("用户不存在" + userId);
        }
        Date startLearnTime = usersVO.getStartLearnTime();
        if (LearningStatus.ifOrdernaryLearn(usersVO.getStatus()) && null == startLearnTime) {
            //强制用户结束学习状态
            throw new CheckRunException("用户学习状态不正确" + userId);
        }
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("learnStatus", usersVO.getStatus());
        if (null == startLearnTime) {
            jsonObject.put("learnTime", 0);
            return jsonObject;
        }
        String pauseKey = RedisKeyConst.getPauseLearnTimeByUserid(userId);
        String continueKey = RedisKeyConst.getContinueLearnTimeByUserid(userId);
        List<Date> pauseList = redisTemplate.opsForList().range(pauseKey, 0, -1);
        log.debug("userid={}， pauseList={}", userId, pauseList);
        List<Date> continueList = redisTemplate.opsForList().range(continueKey, 0, -1);
        log.debug("userid={}， continueList={}", userId, continueList);
        long pause = 0L;
        long continu = 0L;
        if (AssertUtil.isNotEmpty(pauseList)) {
            for (Date p : pauseList) {
                pause += p.getTime();
            }
        }
        if (AssertUtil.isNotEmpty(continueList)) {
            for (Date c : continueList) {
                continu += c.getTime();
            }
        }
        long studySeconds = 0L;
        if (LearningStatus.LEARN_TIMING_PAUSE == LearningStatus.getLearningStatus(usersVO.getStatus())) {
            studySeconds = pause - continu - startLearnTime.getTime();
            log.debug("LEARN_TIMING_PAUSE userid={}， studySeconds={}", userId, studySeconds);
        } else if (LearningStatus.LEARN_TIMING == LearningStatus.getLearningStatus(usersVO.getStatus())) {
            studySeconds = System.currentTimeMillis() + pause - continu - startLearnTime.getTime();
            log.debug("LEARN_TIMING userid={}， studySeconds={}", userId, studySeconds);
        } else if (LearningStatus.LEARN_TIMING_CONINUE == LearningStatus.getLearningStatus(usersVO.getStatus())) {
            studySeconds = System.currentTimeMillis() + pause - continu - startLearnTime.getTime();
            log.debug("LEARN_TIMING_CONINUE userid={}， studySeconds={}", userId, studySeconds);
        } else {
            log.info("else userid={}， studySeconds={}", userId, studySeconds);
            log.info("用户当前学习状态 status={}", LearningStatus.getLearningStatus(usersVO.getStatus()).getTips());
            jsonObject.put("learnTime", 0);
        }
        jsonObject.put("learnTime", studySeconds > 28800000L ? 28800L : studySeconds / 1000);
        return jsonObject;
    }

    public UsersLearnTimingDO getLearnTimingDOById(Long learnId) {
        return usersLearnTimingRepository.findOne(learnId);
    }

    public List<UsersLearnNameHistoryDO> getLearnNameHistory(Long userId, Integer page, Integer pageSize) {
        Specification<UsersLearnNameHistoryDO> specification = new Specification<UsersLearnNameHistoryDO>() {
            @Override
            public Predicate toPredicate(Root<UsersLearnNameHistoryDO> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicateList = Lists.newArrayList();
                predicateList.add(criteriaBuilder.equal(root.get("userId").as(String.class), userId));
                return criteriaBuilder.and(predicateList.toArray(new Predicate[predicateList.size()]));
            }
        };
        Sort sort = new Sort(Sort.Direction.DESC, Lists.newArrayList("createTime"));
        PageRequest pageRequest = new PageRequest(page, pageSize, sort).previous();
        Page<UsersLearnNameHistoryDO> usersStudyInfoDOPage = learnNameHistoryRepository.findAll(specification, pageRequest);
        return usersStudyInfoDOPage.getContent();
    }

    public UsersLearnNameHistoryDO getLearnNameDO(Long currentUserId) {
        return learnNameHistoryRepository.findByUserIdOne(currentUserId);
    }

    public UsersLearnTimingDO getUsersLearnTimingByUserId(Long userId, Integer status) {
        return usersLearnTimingRepository.findByUserIdAndStatusOne(userId, status);
    }

    public Integer countLearnByUserId(Long userId) {
        return usersLearnTimingRepository.countByUserIdAndStatus(userId, 1);
    }

    public UserMonthLearnDO getUserMonthLearnTime(Long userId, String month) {
        if (StringUtils.isBlank(month)) {
            month = CalendarUtil.getDateString(CalendarUtil.getDateHoursBefore(new Date(), 3), CalendarUtil.SHORT_DATE_FORMAT_YYYY_MM);
        }
        UserMonthLearnDO byUserIdAndCountTime = userMonthLearnRepository.findByUserIdAndYearMonths(userId.intValue(), month);
        if (null == byUserIdAndCountTime) {
            byUserIdAndCountTime = new UserMonthLearnDO();
            Integer sumMonth = usersLearnTimingRepository.sumLearningTimeByUserIdAndMonth(userId, month);
            byUserIdAndCountTime.setMonthStudyMinutes(null == sumMonth ? 0 : sumMonth);
            byUserIdAndCountTime.setUserId(userId.intValue());
            return byUserIdAndCountTime;
        }
        return byUserIdAndCountTime;
    }

    public void refreshOldMonthData(String startTime, String endTime, Integer type) throws Exception {
        List<Object[]> oldDayLearnGroupByUserIdAndTypeAndCountTime = usersLearnTimingRepository.getOldDayLearnGroupByUserIdAndTypeAndCountTime(startTime, endTime, type);
        List<SumUser7DaysLearnDO> summaryVOList = EntityUtils.objectToBean(oldDayLearnGroupByUserIdAndTypeAndCountTime, SumUser7DaysLearnDO.class);
        log.info("summaryVOList={}", summaryVOList);
        for (SumUser7DaysLearnDO sumUser7DaysLearnDO : summaryVOList) {
            UserDayLearnDO userDayLearnDO = null;

            UserDayLearnDO byUserIdAndYearMonthsDay = userDayLearnRepository.findByUserIdAndYearMonthsDay(sumUser7DaysLearnDO.getUserId(), sumUser7DaysLearnDO.getYearMonthDay());
            if (null != byUserIdAndYearMonthsDay) {
                userDayLearnDO = byUserIdAndYearMonthsDay;
                if (1 == sumUser7DaysLearnDO.getType()) {
                    userDayLearnDO.setOrdernaryMinutes(byUserIdAndYearMonthsDay.getOrdernaryMinutes() + sumUser7DaysLearnDO.getSumMinutes().intValue());
                } else {
                    userDayLearnDO.setLianmaiMinutes(byUserIdAndYearMonthsDay.getLianmaiMinutes() + sumUser7DaysLearnDO.getSumMinutes().intValue());
                }
            } else {
                userDayLearnDO = new UserDayLearnDO();
                userDayLearnDO.setTargetId(sumUser7DaysLearnDO.getTargetId());
                userDayLearnDO.setUserId(sumUser7DaysLearnDO.getUserId());
                userDayLearnDO.setYearMonthsDay(sumUser7DaysLearnDO.getYearMonthDay());
                userDayLearnDO.setYearMonths(sumUser7DaysLearnDO.getYearMonthDay().substring(0, 7));
                if (1 == sumUser7DaysLearnDO.getType()) {
                    userDayLearnDO.setOrdernaryMinutes(sumUser7DaysLearnDO.getSumMinutes().intValue());
                } else {
                    userDayLearnDO.setLianmaiMinutes(sumUser7DaysLearnDO.getSumMinutes().intValue());
                }
            }
            userDayLearnDO.setStudyMinutes(userDayLearnDO.getOrdernaryMinutes() + userDayLearnDO.getLianmaiMinutes());
            userDayLearnRepository.save(userDayLearnDO);
        }

    }

    public static int getDaysOfMonth(String month) throws ParseException {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM");//设置日期格式
        String date = df.format(new Date());
        if (date.equals(month)) {
            return Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(df.parse(month));
        return calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
    }

    public static void main(String[] args) throws ParseException {
        System.out.println(getDaysOfMonth("2019-02"));
        System.out.println(getDaysOfMonth("2019-01"));
        System.out.println(getDaysOfMonth("2020-02"));
        System.out.println(getDaysOfMonth("2020-03"));
        System.out.println(getDaysOfMonth("2020-04"));
        System.out.println(getDaysOfMonth("2020-05"));
    }

    public UserMonthLearnVO countUserLearnTime(Long userId, String month) throws ParseException {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("总时长");
        UserMonthLearnVO userMonthLearnVO = new UserMonthLearnVO();
        // 月总时长
        UserMonthLearnDO byUserIdAndCountTime = getUserMonthLearnTime(userId, month);
        if (null != byUserIdAndCountTime) {
            userMonthLearnVO.setUserId(userId.intValue());
            userMonthLearnVO.setYearMonths(month);
            userMonthLearnVO.setSumMonthTime(byUserIdAndCountTime.getMonthStudyMinutes());
            userMonthLearnVO.setAvgMonthTime(byUserIdAndCountTime.getMonthStudyMinutes() / getDaysOfMonth(month));
        }
        stopWatch.stop();
        stopWatch.start("每天学习");
        // 月每天学习
        if (StringUtils.isBlank(month)) {
            month = CalendarUtil.getDateString(CalendarUtil.getDateHoursBefore(new Date(), 3), CalendarUtil.SHORT_DATE_FORMAT_YYYY_MM);
            userMonthLearnVO.setYearMonths(month);
        }
        List<UserDayLearnDO> userDayLearnList = userDayLearnRepository.findByUserIdAndYearMonthsOrderByYearMonthsDayASC(userId.intValue(), month);
        if (null != userDayLearnList && userDayLearnList.size() > 0) {
            List<UserMonthLearnVO.DayLearnTime> dayLearnTimeList = Lists.newArrayList();
            userMonthLearnVO.setDayLearnTimeList(dayLearnTimeList);
            userDayLearnList.forEach(e -> {
                dayLearnTimeList.add(new UserMonthLearnVO.DayLearnTime(e.getYearMonthsDay(), e.getOrdernaryMinutes(), e.getLianmaiMinutes()));
            });
        }
        stopWatch.stop();

        List<UserMonthLearnVO.FriendLearnInfo> learnRankList = getLearnRankList(userId, -7);
        if (learnRankList.size() == 1) {
            userMonthLearnVO.setFriendLearnInfoList(new ArrayList<>());
        } else {
            userMonthLearnVO.setFriendLearnInfoList(learnRankList);
        }
        if (learnRankList.isEmpty()) {
            return userMonthLearnVO;
        }
        userMonthLearnVO.setMyself(learnRankList.stream().filter(rank -> rank.getFriendId() == userId.longValue()).findFirst().get());
        log.info("学习统计 {}", stopWatch.prettyPrint());
        return userMonthLearnVO;
    }

    public List<FriendLearnInfoVO> getDayFriendLearnRanking(Long userId, String date) {
        BusinessResult<List<UserFriendInfo>> friendsResult = userFriendsService.findFriendsPage(userId);
        List<UserFriendInfo> friendInfoList = friendsResult.getData();
        List<Long> userIds = new ArrayList<>();
        userIds.add(userId);
        if (!CollectionUtils.isEmpty(friendInfoList)) {
            friendInfoList.forEach(userFriendInfo -> userIds.add(userFriendInfo.getFriendsUserId()));
        }

        List<UserDayLearnDO> dayLearnList = new ArrayList<>();
        List<UsersVO> users = new ArrayList<>();
        int pageCount = (userIds.size() + PAGE_SIZE - 1) / PAGE_SIZE;
        for (int i = 0; i < pageCount; i++) {
            List<Long> subListIds;
            if ((i + 1) == pageCount) {
                subListIds = userIds.subList(i * PAGE_SIZE, userIds.size());
            } else {
                subListIds = userIds.subList(i * PAGE_SIZE, PAGE_SIZE * (i + 1));
            }
            dayLearnList.addAll(userDayLearnRepository.findByYearMonthsDayAndUserIdIn(date, subListIds));
            users.addAll(usersService.findByIdIn(subListIds));
        }

        Map<Long, List<UserDayLearnDO>> listLearnMap = dayLearnList.stream().collect(Collectors.groupingBy(o -> o.getUserId()));
        Map<Long, List<UsersVO>> listUserMap = users.stream().collect(Collectors.groupingBy(o -> o.getId()));

        List<FriendLearnInfoVO> friendLearnInfoVOS = new ArrayList<>();
        userIds.forEach(aLong -> {
            UsersVO usersVO = listUserMap.get(aLong).get(0);
            List<UserDayLearnDO> userDayLearnDOS = listLearnMap.get(aLong);
            UserDayLearnDO userDayLearnDO = new UserDayLearnDO();
            if (Objects.nonNull(userDayLearnDOS)) {
                userDayLearnDO = userDayLearnDOS.get(0);
            }

            FriendLearnInfoVO friendLearnInfoVO = FriendLearnInfoVO.builder().
                    friendId(aLong).
                    friendHead(usersVO.getAvatarUrl()).
                    friendName(usersVO.getName()).
                    learnMinutes(Objects.nonNull(userDayLearnDO.getStudyMinutes()) ? userDayLearnDO.getStudyMinutes() : 0).
                    sex(usersVO.getSex()).
                    status(usersVO.getStatus()).
                    build();
            friendLearnInfoVOS.add(friendLearnInfoVO);
        });

        List<FriendLearnInfoVO> infoVOList = friendLearnInfoVOS.stream().sorted(Comparator.comparing(FriendLearnInfoVO::getLearnMinutes).reversed()).collect(Collectors.toList());
        final int[] i = {1};
        infoVOList.forEach(friendLearnInfoVO -> {
            friendLearnInfoVO.setIndex(i[0]++);
        });

        return infoVOList;
    }

    public List<UserMonthLearnVO.FriendLearnInfo> getLearnRankList(Long userId, Integer day) {
        List<UserMonthLearnVO.FriendLearnInfo> friendLearnInfos = Lists.newArrayList();
        // 7天排行榜
        BusinessResult<List<UserFriendInfo>> friendsResult = userFriendsService.findFriendsPage(userId);
        BusinessResult.checkBusinessResult(friendsResult);
        List<UserFriendInfo> friendInfoList = friendsResult.getData();

        List<Long> friendIdList = Lists.newArrayList();
        if (!friendInfoList.isEmpty()) {
            friendInfoList.forEach(e -> friendIdList.add(e.getFriendsUserId()));
        }
        friendIdList.add(userId);

        String date = CalendarUtil.getDateString(CalendarUtil.addOrBeforeNDay(new Date(), day), CalendarUtil.SHORT_DATE_FORMAT);
        int friendIdSize = friendIdList.size();
        if (day == 0) {
            List<UserDayLearnDO> dayLearnList = userDayLearnRepository.findByYearMonthsDayAndUserIdIn(date, friendIdList);
            List<UsersVO> users = usersService.findByIdIn(friendIdList);
            friendLearnInfos = friendIdList.stream().map(friendId -> {
                UserMonthLearnVO.FriendLearnInfo friendLearnInfo = new UserMonthLearnVO.FriendLearnInfo();
                friendLearnInfo.setFriendId(friendId);
                UserDayLearnDO dayLearnDO = dayLearnList.stream().filter(learn -> learn.getUserId().equals(friendId)).findFirst().orElse(null);
                friendLearnInfo.setLearnMinutes(Objects.nonNull(dayLearnDO) ? dayLearnDO.getStudyMinutes() : 0);
                log.debug("friendId = {} users = {} friendIdList = {}", friendId, users, friendIdList);
                UsersVO userInfo = users.stream().filter(user -> user.getId().equals(friendId)).findFirst().orElseThrow(NullPointerException::new);
                friendLearnInfo.setFriendHead(userInfo.getAvatarUrl());
                friendLearnInfo.setFriendName(userInfo.getName());
                return friendLearnInfo;
            }).collect(Collectors.toList());

        } else {
            for (int i = 0; i < friendIdSize; i++) {
                UserMonthLearnVO.FriendLearnInfo friendLearnInfo = new UserMonthLearnVO.FriendLearnInfo();
                friendLearnInfos.add(friendLearnInfo);
                friendLearnInfo.setFriendId(friendIdList.get(i));
                Integer friend7DayLearnTime = userDayLearnRepository.findUserDayLearnTime(friendIdList.get(i), date);
                friendLearnInfo.setLearnMinutes(friend7DayLearnTime == null ? 0 : friend7DayLearnTime);
                UsersVO usersVO = usersService.find(friendIdList.get(i));
                friendLearnInfo.setFriendHead(usersVO.getAvatarUrl());
                friendLearnInfo.setFriendName(usersVO.getName());
            }
        }
        Collections.sort(friendLearnInfos, (o1, o2) -> o2.getLearnMinutes() - o1.getLearnMinutes());
        for (int i = 0; i < friendIdSize; i++) {
            friendLearnInfos.get(i).setIndex(i + 1);
        }
        return friendLearnInfos;
    }

    public UserDayLearnDO LatestDayLearnRecord(Long userId, String date) {
        return userDayLearnRepository.findByUserIdAndYearMonthsDay(userId, date);
    }

    public UsersStudyInfoDO findLatestStudyInfo(Long userId) {
        return usersStudyInfoRepository.findFirstByUserIdOrderByCreateTimeDesc(userId);
    }

    //TODO方法有问题，无法阅读，参数意义不明
    public UsersLearnNameHistoryDO saveLearnName(UsersDO usersDO, Integer nameStatus, String name) {
        NameStatus nameStatusEnum = NameStatus.getLearningStatus(nameStatus);
        UsersLearnNameHistoryDO learnNameHistoryDO = null;
        List<UsersLearnNameHistoryDO> learnNameHistoryDOList = null;
        UsersLearnTimingDO learnTimingDO = usersLearnTimingRepository.findByUserIdAndStatusOne(usersDO.getId(), YN.YES.getCode());
        Long learnId = null;
        if (null == learnTimingDO) {
            learnNameHistoryDOList = learnNameHistoryRepository.findByUserId(usersDO.getId());
        } else {
            if (nameStatusEnum == NameStatus.LEARN_BEFORE) {
                learnNameHistoryDOList = learnNameHistoryRepository.findByUserIdAndStatusInAndCreateTimeAfter(usersDO.getId(), Lists.newArrayList(NameStatus.LEARN_BEFORE.getCode()), learnTimingDO.getCreateTime());
            } else if (nameStatusEnum == NameStatus.LEARN_TIMING) {
                learnId = usersLearnTimingRepository.findByUserIdOne(usersDO.getId());
                learnNameHistoryDOList = learnNameHistoryRepository.findByUserIdAndStatusInAndCreateTimeAfter(usersDO.getId(), Lists.newArrayList(NameStatus.LEARN_BEFORE.getCode(), NameStatus.LEARN_TIMING.getCode()), learnTimingDO.getCreateTime());
            } else if (nameStatusEnum == NameStatus.LEARN_AFTER) {
                learnId = learnTimingDO.getId();
                learnNameHistoryDOList = learnNameHistoryRepository.findByUserIdAndCreateTimeAfter(usersDO.getId(), learnTimingDO.getCreateTime());
            }
        }

        if (null != learnNameHistoryDOList && learnNameHistoryDOList.size() > 0) {
//            if (learnNameHistoryDOList.size() > 1) {
//                throw new CheckRunException("学习计时名称数据有问题");
//            }
            learnNameHistoryDO = learnNameHistoryDOList.get(0);
        }
        learnNameHistoryDO = editLearnName(usersDO, nameStatus, learnNameHistoryDO, name);
        if (nameStatusEnum != NameStatus.LEARN_BEFORE) {
            usersLearnTimingRepository.updateLearnNameById(name, learnId);
            usersShareService.updateShareContentAndStatusByLearnId(learnId, nameStatus, name, usersDO.getName());
        }
        // 兼容老版本
        String nameKey = RedisKeyConst.getStudyName(usersDO.getId());
        redisTemplate.opsForValue().set(nameKey, name);
        return learnNameHistoryDO;
    }


    public UsersLearnTimingDO saveUsersLearnTiming(UsersLearnTimingDO usersLearnTimingDO) {
        return usersLearnTimingRepository.save(usersLearnTimingDO);
    }

    public UsersLearnNameHistoryDO saveLearnName(Long userId, String studyName) {
        UsersLearnNameHistoryDO exist = learnNameHistoryRepository.findFirstByUserIdAndLearnName(userId, studyName);
        if (Objects.isNull(exist)) {
            UsersLearnNameHistoryDO nameHistory = UsersLearnNameHistoryDO.builder().userId(userId).learnName(studyName).status(NameStatus.LEARN_AFTER.getCode()).type(ORDERNARY_COUNT_TIME.getCode()).build();
            exist = learnNameHistoryRepository.save(nameHistory);
        }
        return exist;
    }

    private UsersLearnNameHistoryDO editLearnName(UsersDO usersDO, Integer nameStatus, UsersLearnNameHistoryDO learnNameHistory, String name) {
        // 如果需要创建的时候名字去重，需要根据userId 先查询是否已经有该名字 ？
        learnNameHistory = new UsersLearnNameHistoryDO();
        UsersLearnNameHistoryDO exist = learnNameHistoryRepository.findFirstByUserIdAndLearnName(usersDO.getId(), name);
        if (Objects.nonNull(exist)) {
            learnNameHistory = exist;
        }
        learnNameHistory.setType(ORDERNARY_COUNT_TIME.code);
        learnNameHistory.setUserId(usersDO.getId());
        learnNameHistory.setLearnName(name);
        learnNameHistory.setStatus(nameStatus);
        return learnNameHistoryRepository.save(learnNameHistory);
    }

    public List<GroupUserStudyTime.UserDayRepTime> queryUserDayStudyTime(GroupUserStudyTime groupUserDayReqTime) {
        List<GroupUserStudyTime.UserDayReqTime> userStudyTimeReqList = groupUserDayReqTime.getUserStudyTimeReqList();
        List<GroupUserStudyTime.UserDayRepTime> userDayRepTimeList = new ArrayList<>();
        for (GroupUserStudyTime.UserDayReqTime userDayReqTime : userStudyTimeReqList) {
            Integer userId = userDayReqTime.getUserId();
            String startTime = userDayReqTime.getStartTime();
            CriteriaBuilder cb = em.getCriteriaBuilder();
            //UsersStudyInfoRepDO指定了查询结果返回至自定义对象
            CriteriaQuery<UsersStudyInfoRepDO> query = cb.createQuery(UsersStudyInfoRepDO.class);
            Root<UsersLearnTimingDO> root = query.from(UsersLearnTimingDO.class);
            Path<Calendar> timePath = root.get("countTime");
            Path<Long> userIdPath = root.get("userId");
            Path<Integer> studyMinutesPath = root.get("studyMinutes");
            List<Predicate> predicateList = new ArrayList<Predicate>();
            if (userId != null) {
                predicateList.add(cb.equal(userIdPath.as(Long.class), userId));
            }
            if (startTime != null) {
                Date n = DateUtil.strToDate(startTime);
                predicateList.add(cb.greaterThanOrEqualTo(timePath.as(Date.class), DateUtil.strToDate(startTime)));
            }
            Predicate[] predicates = new Predicate[predicateList.size()];
            predicates = predicateList.toArray(predicates);
            //加上where条件
            query.where(predicates);
            //指定查询项，select后面的东西
            Expression<String> timeStr = cb.function("DATE_FORMAT", String.class, timePath, cb.parameter(String.class, "formatStr"));
            query.multiselect(timeStr, userIdPath.as(Integer.class), cb.sum(studyMinutesPath));
            query.groupBy(timeStr);
            TypedQuery<UsersStudyInfoRepDO> typedQuery = em.createQuery(query);
            typedQuery.setParameter("formatStr", "%Y-%m-%d");
            List<UsersStudyInfoRepDO> usersStudyInfoRepDOS = typedQuery.getResultList();
            if (null == usersStudyInfoRepDOS || usersStudyInfoRepDOS.size() == 0) {
                continue;
            }
            for (UsersStudyInfoRepDO usersStudyInfoRepDO : usersStudyInfoRepDOS) {
                try {
                    GroupUserStudyTime.UserDayRepTime userDayRepTime = new GroupUserStudyTime.UserDayRepTime();
                    userDayRepTime.setDayTime(usersStudyInfoRepDO.getCountTime());
                    userDayRepTime.setUserId(userDayReqTime.getUserId());
                    Long sumMinutes = usersStudyInfoRepDO.getSumMinutes();
                    if (null == sumMinutes || 0 == sumMinutes) {
                        UsersVO usersVO = usersService.find(userDayReqTime.getUserId().longValue());
                        if (LearningStatus.NO_TIMING == LearningStatus.getLearningStatus(usersVO.getStatus())) {
                            userDayRepTime.setDayHour(new BigDecimal(0));
                        } else if (LearningStatus.ifOrdernaryLearn(usersVO.getStatus())) {
                            JSONObject jsonObject = getLearnTimeByUserId(userDayReqTime.getUserId().longValue());
                            log.info("学习中群用户 userId={}, jsonObject={}", userDayReqTime.getUserId(), jsonObject);
                            Long learnTime = jsonObject.getLong("learnTime");
                            BigDecimal dayHour = CalendarUtil.seconds2Minutes(learnTime.intValue() / 60);
                            userDayRepTime.setDayHour(dayHour);
                        } else if (LearningStatus.getLearningStatus(usersVO.getStatus()) == LearningStatus.LIAN_MAI_TIMING) {
                            Date startLearnTime = usersVO.getStartLearnTime();
                            log.info("连麦中群用户 userId={}, startLearnTime={}", userDayReqTime.getUserId(), startLearnTime);
                            if (null != startLearnTime) {
                                Long learnTime = (System.currentTimeMillis() - startLearnTime.getTime()) / 60000;
                                BigDecimal dayHour = CalendarUtil.seconds2Minutes(learnTime.intValue());
                                userDayRepTime.setDayHour(dayHour);
                            }
                        }
                    } else {
                        userDayRepTime.setDayHour(DateUtil.minutesToHours(sumMinutes));
                    }
                    userDayRepTimeList.add(userDayRepTime);
                } catch (Exception e) {
                    log.error("", e);
                    sendExceptionUtil.sendException("查询群组中用户学习时长异常 userId=" + userId + e.getMessage(), e);
                }
            }
        }
        return userDayRepTimeList;
    }

    /**
     * 增加计时离开的通知
     * 通过延时队列，实现消息指定时间送达
     */
    public void addLeaveMsg(LeaveMsgVO leaveMsg) {
        log.debug("添加计时通知 leaveMsg = {}", leaveMsg);
        //拼接是为了减小value的大小
        String valString = leaveMsg.getUserId() + ":" + leaveMsg.getStartLearnTime();
        redisTemplate.opsForZSet().add(USERS_LEARN_LEAVE_MSG_SET, valString, System.currentTimeMillis());
    }

    /***
     * 计算用户连麦时长
     * @param userId
     * @return
     */
    public Integer totalLiveMinute(Long userId) {
        return userDayLearnRepository.sumLianmaiMinutes(userId);
    }


    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private UsersServiceImpl usersService;
    @Autowired
    private StatusContextFactory statusContextFactory;
    @Autowired
    private TargetServiceImpl targetService;
    @Autowired
    private UsersShareServiceImpl usersShareService;
    @Autowired
    private UserFriendsService userFriendsService;
    @Autowired
    private UserMonthLearnRepository userMonthLearnRepository;
    @Autowired
    private UserDayLearnRepository userDayLearnRepository;
    @PersistenceContext
    private EntityManager em;
    @Autowired
    private UsersStudyInfoRepository usersStudyInfoRepository;
    @Autowired
    private UsersLearnTimingRepository usersLearnTimingRepository;
    @Autowired
    private LearnNameHistoryRepository learnNameHistoryRepository;


}
